package com.beesphere.excel.reader.impls;

import java.io.IOException;
import java.io.InputStream;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;

import com.beesphere.excel.reader.CellFormatter;
import com.beesphere.excel.reader.CellType;
import com.beesphere.excel.reader.ExcelReader;
import com.beesphere.excel.reader.ExcelReaderException;
import com.beesphere.excel.reader.SheetConfig;

public abstract class AbstractExcelReader implements ExcelReader {
	
	protected CellFormatter cellFormatter;
	
	protected abstract void visitStart (int count) throws ExcelReaderException;
	protected abstract void visitStartSheet (int index, String title, int count) throws ExcelReaderException;
	protected abstract boolean visitLine (Object [] line, int lineNum, boolean withHeader) throws ExcelReaderException;
	protected abstract boolean visitEndSheet (int index) throws ExcelReaderException;
	protected abstract void visitEnd () throws ExcelReaderException;
	
	protected SheetConfig sheetConfig = new SheetConfig ();
	
	@Override
	public void read(InputStream input) throws ExcelReaderException {
		try {
			Workbook wb;
			try {
				wb = WorkbookFactory.create (input);
			} catch (InvalidFormatException e) {
				throw new ExcelReaderException(e);
			} 
			int sheetsCount = wb.getNumberOfSheets ();
			visitStart (sheetsCount);
			Sheet sheet = null;
			for (int i = 0; i < sheetsCount; i++) {
				sheet = wb.getSheetAt(i);
				int rowsCount = sheet.getPhysicalNumberOfRows();
				visitStartSheet (i, wb.getSheetName(i), rowsCount);
				if (sheetConfig.isSkip ()) {
					sheetConfig.clean();
					continue;
				}
				boolean withHeader = sheetConfig.isWithHeader();
				int [] columnsIndexes = sheetConfig.getColumnsIndexes ();
				Row row = null;
				for (int j = 0; j < rowsCount; j++) {
					row = sheet.getRow(j);
					int cellsCount = getCellsCount(columnsIndexes, row);
					
					Object [] line = new Object [cellsCount]; 
					Cell cell = null;
					for (int k = 0; k < cellsCount; k++) {
						if (columnsIndexes != null && !validColumn (columnsIndexes, k)) {
							continue;
						}
						cell = row.getCell (k);
						if (cell == null) {
							line[k] = null;
							continue;
						}
						line[k] = getCellValue (i, j, k, cell);
					}
					if (!visitLine (line, j, withHeader)) {
						break;
					}
				}
				sheetConfig.clean();
				if (!visitEndSheet (i)) {
					break;
				}
			}
			visitEnd ();
		} catch (IOException e) {
			throw new ExcelReaderException (e);
		}
		
	}
	
	int getCellsCount (int [] columnsIndexes, Row row) {
		
		int count = 1;
		if (columnsIndexes == null || columnsIndexes.length < 1) {
			return row.getLastCellNum();
		}
		for (int i = 0; i < columnsIndexes.length; i++) {
			count = Math.max(i + 1, count);
		}
		
		return Math.max(count, row.getLastCellNum());
		
	}
	
	boolean validColumn (int[] columnsIndexes, int columnIndex) {
		for (int i : columnsIndexes) {
			if (i == columnIndex) {
				return true;
			}
		}
		return false;
	}
	
	private Object getCellValue (int sheet, int row, int cellNum, Cell cell) throws ExcelReaderException {
		int cellType = cell.getCellType();
		switch (cellType) {
			case Cell.CELL_TYPE_STRING:
				return formatValue (cell.getRichStringCellValue().getString(), CellType.STRING);
			case Cell.CELL_TYPE_NUMERIC:
				if (DateUtil.isCellDateFormatted (cell)) {
					if (cell.getDateCellValue () != null) {
						return formatValue (cell.getDateCellValue (), CellType.DATE);
					}
				} else {
					int iValue = (int)cell.getNumericCellValue();
					if (cell.getNumericCellValue() == iValue) {
						return formatValue (iValue, CellType.INTEGER);
					} else {
						return formatValue (cell.getNumericCellValue(), CellType.DECIMAL);
					}
				}
				break;
			case Cell.CELL_TYPE_BOOLEAN:
				return formatValue (cell.getBooleanCellValue(), CellType.BOOLEAN);
			case Cell.CELL_TYPE_FORMULA:
				return  formatValue (cell.getCellFormula(), CellType.FORMULA);
			case Cell.CELL_TYPE_ERROR:
				return  formatValue (cell.getCellFormula(), CellType.ERROR);
			case Cell.CELL_TYPE_BLANK:
				return  formatValue (cell.getRichStringCellValue().getString(), CellType.STRING);
			default:
				throw new ExcelReaderException ("Unsupported Excel Type [" + cell.getCellType () + "], at Sheet: " + sheet + ", Row: " + row + ", Cell: " + cellNum);
		}
		return null;
	}
	
	private Object formatValue (Object value, CellType type) {
		if (cellFormatter == null || value == null) {
			return value;
		}
		return cellFormatter.format (value, type);
	}
	
	@Override
	public void reset () {
		sheetConfig.clean ();
	}
	
	public CellFormatter getCellFormatter() {
		return cellFormatter;
	}
	public void setCellFormatter(CellFormatter cellFormatter) {
		this.cellFormatter = cellFormatter;
	}

}